package utils

import (
	"testing"

	"github.com/stackrox/rox/central/vulnmgmt/vulnerabilityrequest/common"
	"github.com/stackrox/rox/generated/storage"
	"github.com/stackrox/rox/pkg/fixtures"
	"github.com/stackrox/rox/pkg/protoassert"
	"github.com/stretchr/testify/assert"
)

func TestRequestsWithCoveredScopeWithUnifiedDeferral(t *testing.T) {
	testRequestsWithCoveredScope(t)
}

func testRequestsWithCoveredScope(t *testing.T) {
	globalCVE1DefReq := fixtures.GetGlobalDeferralRequestV2("cve-1")
	globalCVE1FPReq := fixtures.GetGlobalFPRequestV2("cve-1")
	imageScopedCVE1Req := fixtures.GetImageScopeDeferralRequest("reg1", "img1", "1.0", "cve-1")
	allTagCVE1Req := fixtures.GetImageScopeDeferralRequest("reg1", "img1", ".*", "cve-1")
	otherImageReq := fixtures.GetImageScopeDeferralRequest("reg2", "img1", "1.0", "cve-1")

	existingReqs := []*storage.VulnerabilityRequest{
		globalCVE1DefReq,
		globalCVE1FPReq,
		imageScopedCVE1Req,
		allTagCVE1Req,
		otherImageReq,
	}
	for _, tc := range []struct {
		desc            string
		toMatch         *storage.VulnerabilityRequest
		expectedMatches []*storage.VulnerabilityRequest
	}{
		{
			desc:            "match cve-1 requests in covered scopes; global, all tags, specific tag",
			toMatch:         globalCVE1DefReq,
			expectedMatches: existingReqs,
		},
		{
			desc:            "match cve-1 requests in covered scopes; all tags, specific tag",
			toMatch:         allTagCVE1Req,
			expectedMatches: []*storage.VulnerabilityRequest{allTagCVE1Req, imageScopedCVE1Req},
		},
		{
			desc:            "match cve-1 requests in covered scopes; specific tag",
			toMatch:         imageScopedCVE1Req,
			expectedMatches: []*storage.VulnerabilityRequest{imageScopedCVE1Req},
		},
		{
			desc:    "none matched",
			toMatch: fixtures.GetImageScopeDeferralRequest("reg3", "img1", "1.0", "cve-1"),
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			covered := RequestsWithCoveredScope(tc.toMatch, existingReqs)
			protoassert.ElementsMatch(t, tc.expectedMatches, covered)
		})
	}
}

func TestFirstIndexMatchingScopeWithUnifiedDeferral(t *testing.T) {
	testFirstIndexMatchingScope(t)
}

func testFirstIndexMatchingScope(t *testing.T) {
	globalCVE1DefReq := fixtures.GetGlobalDeferralRequestV2("cve-1")
	globalCVE1FPReq := fixtures.GetGlobalFPRequestV2("cve-1")
	imageScopedCVE1Req := fixtures.GetImageScopeDeferralRequest("reg1", "img1", "1.0", "cve-1")
	allTagCVE1Req := fixtures.GetImageScopeDeferralRequest("reg1", "img1", ".*", "cve-1")
	otherImageReq := fixtures.GetImageScopeDeferralRequest("reg2", "img1", "1.0", "cve-1")

	for _, tc := range []struct {
		desc               string
		toMatch            *storage.VulnerabilityRequest
		matchWith          []*storage.VulnerabilityRequest
		expectedFirstIndex int
	}{
		{
			desc:    "match cve-1 requests in covered scopes; global, all tags, specific tag",
			toMatch: globalCVE1DefReq,
			matchWith: []*storage.VulnerabilityRequest{
				otherImageReq,
				allTagCVE1Req,
				imageScopedCVE1Req,
				globalCVE1DefReq,
				globalCVE1FPReq,
			},
			expectedFirstIndex: 3,
		},
		{
			desc:    "match cve-1 requests in covered scopes; all tags, specific tag",
			toMatch: allTagCVE1Req,
			matchWith: []*storage.VulnerabilityRequest{
				otherImageReq,
				allTagCVE1Req,
				imageScopedCVE1Req,
				globalCVE1DefReq,
				globalCVE1FPReq,
			},
			expectedFirstIndex: 1,
		},
		{
			desc:    "match cve-1 requests in covered scopes; specific tag",
			toMatch: imageScopedCVE1Req,
			matchWith: []*storage.VulnerabilityRequest{
				otherImageReq,
				allTagCVE1Req,
				imageScopedCVE1Req,
				globalCVE1DefReq,
				globalCVE1FPReq,
			},
			expectedFirstIndex: 1,
		},
		{
			desc:               "none matched",
			toMatch:            fixtures.GetImageScopeDeferralRequest("reg3", "img1", "1.0", "cve-1"),
			matchWith:          []*storage.VulnerabilityRequest{allTagCVE1Req, imageScopedCVE1Req},
			expectedFirstIndex: -1,
		},
		{
			desc:               "none matched again",
			toMatch:            fixtures.GetImageScopeDeferralRequest("reg3", "img1", "1.0", "cve-1"),
			expectedFirstIndex: -1,
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			idx := FirstIndexMatchingScope(tc.toMatch, tc.matchWith)
			assert.Equal(t, tc.expectedFirstIndex, idx)
		})
	}
}

func TestIsUpdateNoOp(t *testing.T) {
	globalCVE1DefReq := fixtures.GetGlobalDeferralRequestV2("cve-1")
	globalCVE1FPReq := fixtures.GetGlobalFPRequestV2("cve-1")

	for _, tc := range []struct {
		desc    string
		request *storage.VulnerabilityRequest
		update  *common.UpdateRequest
		noop    bool
	}{
		{
			desc:    "no-op deferral update",
			request: globalCVE1DefReq,
			update: &common.UpdateRequest{
				DeferralUpdate: &storage.DeferralUpdate{
					CVEs:   []string{"cve-1"},
					Expiry: globalCVE1DefReq.GetDeferralReq().GetExpiry(),
				},
			},
			noop: true,
		},
		{
			desc:    "no-op false positive update",
			request: globalCVE1FPReq,
			update: &common.UpdateRequest{
				FalsePositiveUpdate: &storage.FalsePositiveUpdate{
					CVEs: []string{"cve-1"},
				},
			},
			noop: true,
		},
		{
			desc:    "allow deferral update",
			request: fixtures.GetGlobalDeferralRequestV2("cve-1", "cve-2"),
			update: &common.UpdateRequest{
				DeferralUpdate: &storage.DeferralUpdate{
					CVEs:   []string{"cve-1"},
					Expiry: &storage.RequestExpiry{Expiry: &storage.RequestExpiry_ExpiresWhenFixed{ExpiresWhenFixed: true}},
				},
			},
			noop: false,
		},
		{
			desc:    "allow false positive update",
			request: fixtures.GetGlobalFPRequestV2("cve-1"),
			update: &common.UpdateRequest{
				FalsePositiveUpdate: &storage.FalsePositiveUpdate{
					CVEs: []string{"cve-1", "cve-2"},
				},
			},
			noop: false,
		},
		{
			desc: "nil",
			noop: true,
		},
		{
			desc:    "nil again",
			request: fixtures.GetGlobalDeferralRequestV2("cve-1", "cve-2"),
			noop:    true,
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			assert.Equal(t, tc.noop, IsUpdateNoOp(tc.request, tc.update))
		})
	}
}
